var Fiber = require('fibers');

// create a new fiber to call fn
exports.launchFiber = function(fn) {
    if (typeof fn !== 'function') throw new Error('first argument must be a function');
    var newargs = Array.prototype.slice.call(arguments, 1);
    Fiber(function() {
        fn.apply(null, newargs);
    }).run();
};

// async to sync
exports.forMethod = function(obj, methodName) {
    var method = obj[methodName];
    if (!method) throw new Error('forMethod: Not method in object');

    var newargs = Array.prototype.slice.call(arguments, 2);
    return applyAndWait(obj, method, newargs);
}

function applyAndWait(obj, fn, args) {
    var fiber = Fiber.current;
    if (!fiber) throw new Error('applyAndWait can only be called inside a fiber');

    var fnName = fn.name;
    var resumeCallback = function(err, data) {
        if (fiber.callbackAlreadyCalled)
            throw new Error("Callback for function " + fnName + " called twice.");
        fiber.callbackAlreadyCalled = true;
        fiber.err = err;
        fiber.data = data;
        try {
            fiber.run();
        } catch (e) {
            if (e.message == "This Fiber is already running") null;
            else throw e;
        }
    };

    args.push(resumeCallback);

    fiber.callbackAlreadyCalled = false;
    fn.apply(obj, args);

    if (!fiber.callbackAlreadyCalled) {
        Fiber.yield();
    }

    if (fiber.err) throw fiber.err;
    return fiber.data;
}

exports.getRandomNum = function(min, max) {
    var range = max - min;
    var rand = Math.random();
    return(min + Math.round(rand * range));
};

exports.invokeCallback = function(cb) {
    cb = cb || function() {};
    cb.apply(null, Array.prototype.slice.call(arguments, 1));
};